﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AppLimit.CloudComputing.SharpBox;
using AppLimit.CloudComputing.SharpBox.StorageProvider.DropBox;
using System.Reflection;
using System.IO;
using InnovTech.Util.Crypto;

namespace InnovTech.Data.Dropbox
{
    /// <summary>
    /// A wrapper class of CloudStorage for Dropbox
    /// </summary>
    public class DropboxClient
    {
        
        /// <summary>
        /// Represents the WeakReference container for the <see cref="CloudStorage"/> instance.
        /// </summary>
        private WeakReference _storageRef;

        /// <summary>
        /// Represents the Login infos for this Dropbox application.
        /// </summary>
        private DropboxLoginInfo _loginInfo;

        /// <summary>
        /// Represents the Cryptor for the serialization.
        /// </summary>
        private StreamCryptor _cryptor;

        /// <summary>
        /// Gets or sets the <see cref="CloudStorage"/> instance of this Dropbox application.
        /// </summary>
        private CloudStorage Storage
        {
            get
            {
                if (_storageRef == null || !_storageRef.IsAlive)
                    return null;
                return _storageRef.Target as CloudStorage;
            }
            set
            {
                _storageRef = new WeakReference(value);
            }
        }

        public DropboxClient(DropboxLoginInfo login, StreamCryptor cryptor)
        {
            _loginInfo = login;
            _cryptor = cryptor;
        }

        #region Open Storage
        /// <summary>
        /// Indicates whether the <see cref="CloudStorage"/> instance is opened.
        /// </summary>
        public bool IsOpened
        {
            get
            {
                if (_storageRef == null || _storageRef.Target == null || !_storageRef.IsAlive)
                    return false;
                return (_storageRef.Target as CloudStorage).IsOpened;
            }
        }

        

        public void Open()
        {
            if (!IsOpened)
            {
                CloudStorage storage = null;
                bool opened = Open(_loginInfo.AppKey, _loginInfo.AppSecret, _loginInfo.UserName, _loginInfo.Password, out storage);
                if (!opened)
                    throw new Exception("Can not open the connection to the Dropbox server");
                Storage = storage;
            }
        }

        public bool Open(string appKey, string appSecret, string userName, string password, out CloudStorage storage)
        {
            ICloudStorageAccessToken accessToken = CreateAccessToken(appKey, appSecret, userName, password);
            return Open(accessToken, out storage);
        }

        public bool Open(ICloudStorageAccessToken token, out CloudStorage storage)
        {
            storage = new CloudStorage();
            // get the right config
            ICloudStorageConfiguration cfg = CloudStorage.GetCloudConfigurationEasy(nSupportedCloudConfigurations.DropBox);

            // try to open the box
            storage.Open(cfg, token);
            return storage.IsOpened;
        }


        public ICloudStorageAccessToken CreateAccessToken(string appKey, string appSecret, string userName, string password)
        {
            DropBoxBaseTokenInformation info = new DropBoxBaseTokenInformation()
            {
                ConsumerKey = appKey,
                ConsumerSecret = appSecret
            };

            Assembly assembly = Assembly.GetAssembly(typeof(CloudStorage));
            Type type = assembly.GetType("AppLimit.CloudComputing.SharpBox.StorageProvider.DropBox.DropBoxToken");

            ConstructorInfo cInfo = type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(string), typeof(string), typeof(DropBoxBaseTokenInformation) }, null);
            object dbTokenObj = cInfo.Invoke(new object[] { userName, password, info });
            return dbTokenObj as ICloudStorageAccessToken;
        }
        #endregion Open Storage

        public void Close()
        {
            if (IsOpened)
                Storage.Close();
        }

        

        public void Delete(string targetFileName, string targetDir)
        {
            var appFolder = Storage.GetFolder(targetDir);
            var file = Storage.GetFile(targetFileName, appFolder);
            Storage.DeleteFileSystemEntry(file);
        }

        public void Upload(string localFilePath, string targetDir, string targetFileName, FileOperationProgressChanged progressChanged = null)
        {
            var appFolder = Storage.GetFolder(targetDir);
            if (progressChanged == null)
                Storage.UploadFile(localFilePath, appFolder);
            else
                Storage.UploadFile(localFilePath, appFolder, progressChanged);
        }

        public void Download(string fileName, string parent, string targetPath, FileOperationProgressChanged progressChanged = null)
        {
            ICloudDirectoryEntry dir = Storage.GetFolder(parent);
            if(progressChanged == null)
                Storage.DownloadFile(dir, fileName, targetPath);
            else
                Storage.DownloadFile(dir, fileName, targetPath, progressChanged);
            Storage.Close();
        }

        public ICloudFileSystemEntry Serialize(object obj, string targetFileName, string targetDir, FileOperationProgressChanged progressChanged = null)
        {
            var appFolder = Storage.GetFolder(targetDir);
            string fileName = Path.GetFileName(targetFileName);

            using (MemoryStream encrypted = _cryptor.Encrypt(obj))
            {
                encrypted.Position = 0;


                ICloudFileSystemEntry file;

                if (progressChanged == null)
                    file = Storage.UploadFile(encrypted, fileName, appFolder);
                else
                    file = Storage.UploadFile(encrypted, fileName, appFolder, progressChanged);
                encrypted.Close();
                return file;
            }
        }

        public object Deserialize(string targetFileName, string targetDir)
        {
            var appFolder = Storage.GetFolder(targetDir);
            string fileName = Path.GetFileName(targetFileName);
            var appFile = GetFile(fileName, appFolder);
            if (appFile == null)
                throw new ArgumentException("no file with the given name is on the server");

            using (MemoryStream mem = new MemoryStream())
            {
                Storage.DownloadFile(fileName, appFolder, mem);
                mem.Position = 0;
                object decrypted = _cryptor.Decrypt(mem);

                mem.Close();
                return decrypted;
            }
        }

        public ICloudFileSystemEntry GetFile(string file, ICloudDirectoryEntry dir)
        {
            var q = from fe in dir
                    where fe.Name.Equals(file)
                    select fe;
            if (q.Any())
                return q.First();
            return null;
        }

        public ICloudDirectoryEntry GetFolder(string dir)
        {
            return Storage.GetFolder(dir);
        }

        public void Execute(params Action[] actions)
        {

            if (actions == null || actions.Length == 0)
                return;

            try
            {
                Open();
                foreach (var action in actions)
                    action();
            }
            catch
            {
                throw;
            }
            finally
            {
                Close();
            }
        }

    }
}
